/**
 * @file build.c
 * @author 逆流 (1171267147@qq.com)
 * @brief 将boot程序组装为启动镜像
 * @date 2025-07-25
 * 
 * @copyright Copyright (c) 2025
 * @parblock
 * @version 0.1 组装bootsec和setup为镜像文件
 * @endparblock
 */
#include <fcntl.h>
#include <linux/fs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define MINIX_HEADER 32
#define GCC_HEADER 1024

#define SYS_SIZE 0x2000

#define DEFAULT_MAJOR_ROOT 2
#define DEFAULT_MINOR_ROOT 29

#define SETUP_SECTS 4

#define STRINGIFY(x) #x

#define ARG_LEN 4

void die(char *str) {
    fprintf(stderr, "%s\n", str);
    exit(1);
}

void usage(void) {
    die("Usage: build bootsect setup system [rootdev] [> image]");
}

int main(int argc, char **argv) {
    int i, c, id;
    char buf[1024];
    char major_root, minor_root;
    struct stat sb;

    if ((argc != ARG_LEN) && (argc != ARG_LEN + 1))
        usage();

    // 设置根设备号,用于加载根文件系统
    if (argc == ARG_LEN + 1) {
        if (strcmp(argv[ARG_LEN], "FLOPPY")) {
            if (stat(argv[ARG_LEN], &sb)) {
                perror(argv[ARG_LEN]);
                die("Couldn't stat root device");
            }
            major_root = sb.st_rdev & 0xff00;
            minor_root = sb.st_rdev & 0x00ff;
        } else {
            major_root = 0;
            minor_root = 0;
        }
    } else {
        major_root = DEFAULT_MAJOR_ROOT;
        minor_root = DEFAULT_MINOR_ROOT;
    }
    fprintf(stderr, "Root device is (%d, %d)\n", major_root, minor_root);

    if ((major_root != 2) && (major_root != 3) && (major_root != 0)) {
        fprintf(stderr, "Illegal root device (major = %d)\n", major_root);
        die("Bad root device --- major #");
    }

    for (i = 0; i < sizeof(buf); i++)
        buf[i] = 0;

    if ((id = open(argv[1], O_RDONLY, 0)) < 0)
        die("Unable to open 'boot'");

    // 将bootsect中的内容读入buf
    i = read(id, buf, sizeof(buf));
    fprintf(stderr, "Boot sector %d bytes.\n", i);
    if (i != 512)
        die("Boot block must be exactly 512 bytes");

    buf[508] = (char)minor_root;
    buf[509] = (char)major_root;

    // *step1 : 将bootsec的内容拷贝到镜像文件,大小为512字节,放置在第一个扇区

    // 将buf中的内容写到stdout(fd = 1)
    //! 这里写到stdout是为了方便通过重定向,将数据写入到镜像文件
    i = write(1, buf, 512);
    if (i != 512)
        die("Write call failed");
    close(id);

    if ((id = open(argv[2], O_RDONLY, 0)) < 0)
        die("Unable to open 'setup'");

    // 循环读取setup中的数据buf中,然后追加到stdout;同时使用i记录读出数据的长度
    for (i = 0; (c = read(id, buf, sizeof(buf))) > 0; i += c)
        if (write(1, buf, c) != c)
            die("Write call failed");

    close(id);

    // *step2: 将setup中的内容追加到镜像文件中,大小为4x512字节,放置在2-5号扇区

    // setup长度最大允许四个扇区
    if (i > SETUP_SECTS * 512)
        die("Setup exceeds" STRINGIFY(
            SETUP_SECTS) " sectors - rewrite build/boot/setup");
    fprintf(stderr, "Setup is %d bytes.\n", i);

    // 清空buf
    for (c = 0; c < sizeof(buf); c++)
        buf[c] = '\0';

    while (i < SETUP_SECTS * 512) {
        c = SETUP_SECTS * 512 - i;  // setup不足四个扇区的空余字节数
        if (c > sizeof(buf))
            c = sizeof(buf);
        // 使用0来填充多余的扇区
        if (write(1, buf, c) != c)
            die("Write call failed");
        i += c;  // 已被填充的长度
    }

    // *step3: 拷贝system的内容追加到镜像文件中
#if 0
    if ((id = open(argv[3], O_RDONLY, 0)) < 0)
        die("Unable to open 'system'");
    for (i = 0; (c = read(id, buf, sizeof(buf))) > 0; i += c)
        if (write(1, buf, c) != c)
            die("Write call failed");
    close(id);

    fprintf(stderr, "System is %d bytes.\n", i);
    if (i > SYS_SIZE * 16)
        die("System is too big");
#endif
    return 0;
}
